Skip to content

Comments

[FIX] 캠페인 제안 양방향 고려 이벤트 및 분리 설계#394

Merged
1000hyehyang merged 2 commits intodevfrom
fix/391-chat
Feb 12, 2026
Merged

[FIX] 캠페인 제안 양방향 고려 이벤트 및 분리 설계#394
1000hyehyang merged 2 commits intodevfrom
fix/391-chat

Conversation

@1000hyehyang
Copy link
Member

Summary

Changes

Type of Change

  • Bug fix (기존 기능에 영향을 주지 않는 버그 수정)
  • New feature (기존 기능에 영향을 주지 않는 새로운 기능 추가)
  • Breaking change (기존 기능에 영향을 주는 수정)
  • Refactoring (기능 변경 없는 코드 개선)
  • Documentation (문서 수정)
  • Chore (빌드, 설정 등 기타 변경)
  • Release (develop → main 배포)

Related Issues

참고 사항

@1000hyehyang 1000hyehyang self-assigned this Feb 12, 2026
@1000hyehyang 1000hyehyang added the bug Something isn't working label Feb 12, 2026
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @1000hyehyang, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

이 Pull Request는 캠페인 제안 관련 이벤트 처리 로직을 개선하고 채팅방 생성 기능을 명확하게 분리하여 시스템의 견고성과 유연성을 높입니다. 특히, 캠페인 제안 이벤트에 행위자 정보를 추가하고, 채팅방 생성 시 사용자 권한 검증 여부에 따라 다른 메서드를 사용하도록 재설계함으로써, 이벤트 기반의 자동 채팅방 생성과 사용자 요청에 의한 채팅방 생성을 구분합니다. 또한, 트랜잭션 후 실행 로직의 안정성을 향상시켜 예상치 못한 오류를 줄였습니다.

Highlights

  • 이벤트 객체 확장: 캠페인 제안 이벤트(CampaignProposalSentEvent)에 actorUserId 필드가 추가되어 제안의 행위자 정보를 명확히 전달할 수 있게 되었습니다.
  • 채팅방 생성 로직 분리: 채팅방 생성 서비스(ChatRoomCommandService)가 사용자 요청용(createOrGetRoomAsMember)과 시스템/이벤트용(createOrGetRoomSystem)으로 명확하게 분리되었습니다. 시스템용 메서드는 권한 검증 없이 새로운 트랜잭션에서 실행됩니다.
  • 이벤트 리스너 업데이트: 캠페인 제안 및 신청 이벤트 리스너(CampaignApplySentEventListener, CampaignProposalSentEventListener)가 이제 시스템용 채팅방 생성 메서드(createOrGetRoomSystem)를 사용하여 이벤트 기반의 채팅방 자동 생성을 처리합니다.
  • 트랜잭션 후 실행 안정성 강화: SpringAfterCommitExecutor가 활성 트랜잭션이 없는 경우 IllegalStateException을 발생시키는 대신 경고를 로깅하고 태스크를 즉시 실행하도록 변경되어 시스템 안정성이 향상되었습니다.
Changelog
  • src/main/java/com/example/RealMatch/business/application/event/CampaignProposalSentEvent.java
    • actorUserId 필드가 추가되었습니다.
  • src/main/java/com/example/RealMatch/business/application/service/CampaignProposalService.java
    • CampaignProposalSentEvent 생성 시 proposal.getSenderUserId()를 사용하여 actorUserId를 설정하도록 업데이트되었습니다.
  • src/main/java/com/example/RealMatch/chat/application/event/apply/CampaignApplySentEventListener.java
    • ensureRoomAndGetId 메서드 내에서 chatRoomCommandService.createOrGetRoom 호출이 createOrGetRoomSystem으로 변경되었습니다.
    • 주석이 '이벤트 기반 자동 생성이므로 createOrGetRoomSystem 사용 (권한 검증 없음).'으로 업데이트되었습니다.
  • src/main/java/com/example/RealMatch/chat/application/event/proposal/CampaignProposalSentEventListener.java
    • ensureRoomAndGetId 메서드의 시그니처가 (Long brandUserId, Long creatorUserId)에서 (CampaignProposalSentEvent event)로 변경되었습니다.
    • chatRoomCommandService.createOrGetRoom 호출이 createOrGetRoomSystem으로 변경되었습니다.
    • 주석이 '이벤트 기반 자동 생성이므로 createOrGetRoomSystem 사용 (권한 검증 없음).'으로 업데이트되었습니다.
  • src/main/java/com/example/RealMatch/chat/application/service/room/ChatRoomCommandService.java
    • 기존 createOrGetRoom 메서드가 createOrGetRoomAsMember로 이름이 변경되고, createOrGetRoomSystem이라는 새로운 메서드가 추가되었습니다.
    • 각 메서드에 대한 Javadoc이 추가되어 사용 목적을 명확히 했습니다.
  • src/main/java/com/example/RealMatch/chat/application/service/room/ChatRoomCommandServiceImpl.java
    • createOrGetRoomAsMembercreateOrGetRoomSystem 메서드가 구현되었습니다.
    • createOrGetRoomSystem 메서드에 Propagation.REQUIRES_NEW 트랜잭션 전파 속성이 추가되었습니다.
    • 기존 validateRequest 메서드가 validateMemberRequestvalidateSystemRequest로 분리되어 각 메서드의 유효성 검증 로직을 담당하도록 재구성되었습니다.
    • 실제 채팅방 생성/조회 로직을 담당하는 findOrCreateRoom private 메서드가 추가되었습니다.
  • src/main/java/com/example/RealMatch/chat/infrastructure/tx/SpringAfterCommitExecutor.java
    • 트랜잭션이 활성화되지 않은 경우 IllegalStateException을 발생시키는 대신 경고를 로깅하고 태스크를 즉시 실행하도록 로직이 변경되었습니다.
    • 즉시 실행 중 발생하는 예외를 처리하는 try-catch 블록이 추가되었습니다.
  • src/main/java/com/example/RealMatch/chat/presentation/rest/controller/ChatController.java
    • createOrGetRoom 엔드포인트에서 chatRoomCommandService.createOrGetRoom 호출이 createOrGetRoomAsMember로 변경되었습니다.
Activity
  • 이 Pull Request에 대한 활동 내역은 제공되지 않았습니다.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@1000hyehyang 1000hyehyang merged commit e3bc0df into dev Feb 12, 2026
1 check passed
@1000hyehyang 1000hyehyang deleted the fix/391-chat branch February 12, 2026 10:56
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This PR aims to improve campaign proposal event processing and chat room creation logic. However, a high-severity broken access control vulnerability was identified. The new createOrGetRoomSystem method bypasses authorization checks, which, when used by event listeners, could allow an attacker to create chat rooms between arbitrary users if they can trigger the corresponding application events. This poses a significant privacy risk and requires remediation by ensuring authorization is performed in all chat room creation paths. On the positive side, the refactoring clarifies the proposal subject by adding actorUserId to CampaignProposalSentEvent, impressively separates ChatRoomCommandService for user requests and system events, and enhances stability by using Propagation.REQUIRES_NEW for transaction isolation in event listeners. Additionally, a suggestion to extract duplicate validation logic, aligning with best practices for maintainability, was noted. Overall, code clarity and stability have greatly improved, but the security vulnerability needs immediate attention.

Comment on lines +44 to +49
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public ChatRoomCreateResponse createOrGetRoomSystem(Long brandId, Long creatorId) {
validateSystemRequest(brandId, creatorId);
return findOrCreateRoom(brandId, creatorId);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

The new method createOrGetRoomSystem is explicitly designed to bypass authorization checks, as confirmed by its implementation of validateSystemRequest which only performs basic validation, not permission verification. This method is intended for internal system/event use, but it creates a dangerous backdoor for creating chat rooms. Any part of the application that can call this method can create chat rooms between arbitrary users. This violates the principle of least privilege and secure design. The previous implementation enforced that the user creating the room was a member of that room.

Comment on lines 55 to 58
private Long ensureRoomAndGetId(Long brandUserId, Long creatorUserId) {
return chatRoomCommandService
.createOrGetRoom(brandUserId, brandUserId, creatorUserId)
.createOrGetRoomSystem(brandUserId, creatorUserId)
.roomId();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

The ensureRoomAndGetId method was changed to use createOrGetRoomSystem, which, as noted in the comments, bypasses authorization checks. This method is called by the handleCampaignApplySent event listener. The listener trusts the brandUserId and creatorUserId from the CampaignApplySentEvent without verifying if the user who triggered the event is authorized to create a chat room for those users. This could allow an attacker who finds a way to publish a malicious CampaignApplySentEvent to create chat rooms between arbitrary users, leading to a breach of privacy. The previous implementation correctly passed the actor's userId to enforce authorization.

Comment on lines +85 to 88
private Long ensureRoomAndGetId(CampaignProposalSentEvent event) {
return chatRoomCommandService
.createOrGetRoom(brandUserId, brandUserId, creatorUserId)
.createOrGetRoomSystem(event.brandUserId(), event.creatorUserId())
.roomId();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

The ensureRoomAndGetId method uses createOrGetRoomSystem, which bypasses authorization checks, based on data from a CampaignProposalSentEvent. Although the event was updated to include an actorUserId, this ID is not used to perform any authorization checks before creating the chat room. The listener implicitly trusts the event's content. This could allow an attacker who finds a way to publish a malicious CampaignProposalSentEvent to create chat rooms between arbitrary users, leading to a breach of privacy.

Suggested change
private Long ensureRoomAndGetId(CampaignProposalSentEvent event) {
return chatRoomCommandService
.createOrGetRoom(brandUserId, brandUserId, creatorUserId)
.createOrGetRoomSystem(event.brandUserId(), event.creatorUserId())
.roomId();
private Long ensureRoomAndGetId(CampaignProposalSentEvent event) {
return chatRoomCommandService
.createOrGetRoomAsMember(event.actorUserId(), event.brandUserId(), event.creatorUserId())
.roomId();
}

Comment on lines +51 to +64
private void validateMemberRequest(Long userId, Long brandId, Long creatorId) {
if (brandId == null || creatorId == null || brandId.equals(creatorId)) {
throw new CustomException(ChatErrorCode.INVALID_ROOM_REQUEST);
}
if (!userId.equals(brandId) && !userId.equals(creatorId)) {
throw new CustomException(ChatErrorCode.NOT_ROOM_MEMBER);
}
}

private void validateSystemRequest(Long brandId, Long creatorId) {
if (brandId == null || creatorId == null || brandId.equals(creatorId)) {
throw new CustomException(ChatErrorCode.INVALID_ROOM_REQUEST);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

validateMemberRequestvalidateSystemRequest 메서드에 중복된 유효성 검사 로직이 있습니다. 이 공통 로직을 별도의 private 메서드로 추출하여 중복을 제거하고 코드의 유지보수성을 높이는 것을 제안합니다.

    private void validateRoomParticipants(Long brandId, Long creatorId) {
        if (brandId == null || creatorId == null || brandId.equals(creatorId)) {
            throw new CustomException(ChatErrorCode.INVALID_ROOM_REQUEST);
        }
    }

    private void validateMemberRequest(Long userId, Long brandId, Long creatorId) {
        validateRoomParticipants(brandId, creatorId);
        if (!userId.equals(brandId) && !userId.equals(creatorId)) {
            throw new CustomException(ChatErrorCode.NOT_ROOM_MEMBER);
        }
    }

    private void validateSystemRequest(Long brandId, Long creatorId) {
        validateRoomParticipants(brandId, creatorId);
    }
References
  1. Extract duplicate validation logic into a separate component (e.g., a validator class) to reduce code duplication and improve maintainability.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant